package com.ruoyi.wetest.stats;

import com.codahale.metrics.*;
import com.ruoyi.wetest.stats.metrics.WeTestTimeClock;
import com.ruoyi.wetest.utils.WeTestConst;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author Allen
 * @description 单个消息统计信息
 * @Date 2024/5/6 14:44
 */
public class CodeStat implements MetricSet {

    /**
     * 编号
     */
    private final Integer number;

    /**
     * 事务名称
     */
    private final String transName;

    /**
     * 发送和接收都有时 TPS 和 耗时. rt packs and tps
     */
    private final Timer rxtx;

    /**
     * 定时统计Histogram
     */
    private final Timer rxtx2;

    /**
     * 所有接收的总消息统计
     */
    private final OneWayStat oneWayRx;

    public Integer getNumber() {
        return number;
    }

    public String getTransName() {
        return transName;
    }

    public Timer getRxtx() {
        return rxtx;
    }

    public OneWayStat getOneWayRx() {
        return oneWayRx;
    }

    public Timer getRxtx2() {
        return rxtx2;
    }

    public WeTestUniform2Reservoir getUniform2Reservoir() {
        return uniform2Reservoir;
    }

    private final AtomicLong lastUpdateTime;
    private final WeTestUniform2Reservoir uniform2Reservoir;


    public void updateTimer(long duration, TimeUnit unit) {
        lastUpdateTime.set(System.nanoTime());
        rxtx.update(duration, unit);
        this.rxtx2.update(duration, unit);
    }

    public CodeStat(Integer number, String transName) {
        this.number = number;
        this.transName = transName;
        this.oneWayRx = new OneWayStat();
        this.lastUpdateTime = new AtomicLong(System.nanoTime()-1000000000 );
        uniform2Reservoir = new WeTestUniform2Reservoir(1028);
        this.rxtx = new Timer(uniform2Reservoir, new WeTestTimeClock(this.lastUpdateTime));
        this.rxtx2 = new Timer(new SlidingTimeWindowReservoir(5, TimeUnit.SECONDS), new WeTestTimeClock(this.lastUpdateTime));
    }

    protected void putMetrics(Map<String, Metric> metrics) {

    }

    @Override
    public Map<String, Metric> getMetrics() {
        final Map<String, Metric> metrics = new HashMap<>();
        metrics.put("number", (Gauge<Integer>) () -> number);
        metrics.put("transName", (Gauge<String>) () -> transName);
        metrics.put("tps", rxtx);

        metrics.put("RX-packets", oneWayRx.getPacks());
        metrics.put("Succ-packets", oneWayRx.getSucc());
        metrics.put("Succ-Rate", (Gauge<Double>) oneWayRx::getSuccRate);
        metrics.put("Fail-packets", oneWayRx.getFail());
        metrics.put("Error-packets", oneWayRx.getError());
        metrics.put("Timeout-packets", oneWayRx.getTimeout());

        putMetrics(metrics);

        return Collections.unmodifiableMap(metrics);
    }

    /**
     * 单向消息统计信息
     */
    public static final class OneWayStat {
        /**
         * 消息个数
         */
        private final Counter packs;
        /**
         * 成功个数
         */
        private final Counter succ;
        /**
         * 失败个数
         */
        private final Counter fail;

        /**
         * 错误个数
         */
        private final Counter error;

        /**
         * 超时个数
         */
        private final Counter timeout;

        public OneWayStat() {
            this.packs = new Counter();
            this.succ = new Counter();
            this.fail = new Counter();
            this.error = new Counter();
            this.timeout = new Counter();
        }

        public Counter getPacks() {
            return packs;
        }

        public Counter getSucc() {
            return succ;
        }

        public Counter getFail() {
            return fail;
        }

        public Counter getError() {
            return error;
        }

        public Counter getTimeout() {
            return timeout;
        }

        /**
         * 成功率
         *
         * @return
         */
        public double getSuccRate() {
            if (packs.getCount() == 0L || succ.getCount() == 0L) return 0;
            return (double) succ.getCount() / (double) packs.getCount();
        }

        public void update(int status) {
            packs.inc();
            switch (status) {
                case WeTestConst.TRANS_FAIL_STATUS:
                    fail.inc();
                    break;
                case WeTestConst.Trans_SUCC_Status:
                    succ.inc();
                    break;
                case WeTestConst.TRANS_ERROR_STATUS:
                    error.inc();
                    break;
                case WeTestConst.Trans_TIMEOUT_Status:
                    timeout.inc();
                    break;
            }

        }
    }
}
